﻿using System;
using System.Runtime.InteropServices;

namespace CabConfigProvisioning
{
    public class FdiFunctions : IDisposable
    {
        [StructLayout(LayoutKind.Sequential)]
        protected class CabError
        {
            public int erfOper;
            public int erfType;
            public int fError;
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
        protected class FdiNotification
        {
            public int cb;
            public string psz1;
            public string psz2;
            public string psz3;
            public IntPtr userData;
            public IntPtr hf;
            public ushort date;
            public ushort time;
            public ushort attribs;   // TODO flag for UTF8 encoding available
            public ushort setID;
            public ushort iCabinet;
            public ushort iFolder;
            public int fdie;
        }

        protected enum FdiNotificationType
        {
            CabinetInfo,
            PartialFile,
            CopyFile,
            CloseFileInfo,
            NextCabinet,
            Enumerate
        }

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        protected delegate IntPtr FdiMemAllocDelegate(int numBytes);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        protected delegate void FdiMemFreeDelegate(IntPtr mem);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        protected delegate IntPtr FdiFileOpenDelegate(string fileName, int oflag, int pmode);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        protected delegate Int32 FdiFileReadDelegate(IntPtr hf,
                                                  [In, Out] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2,
                                                      ArraySubType = UnmanagedType.U1)] byte[] buffer, int cb);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        protected delegate Int32 FdiFileWriteDelegate(IntPtr hf,
                                                   [In] [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 2,
                                                       ArraySubType = UnmanagedType.U1)] byte[] buffer, int cb);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        protected delegate Int32 FdiFileCloseDelegate(IntPtr hf);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        protected delegate Int32 FdiFileSeekDelegate(IntPtr hf, int dist, int seektype);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        protected delegate IntPtr FdiNotifyDelegate(
            FdiNotificationType fdint, [In] [MarshalAs(UnmanagedType.LPStruct)] FdiNotification fdin);

        [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FDICreate", CharSet = CharSet.Ansi)]
        protected static extern IntPtr FdiCreate(
            FdiMemAllocDelegate fnMemAlloc,
            FdiMemFreeDelegate fnMemFree,
            FdiFileOpenDelegate fnFileOpen,
            FdiFileReadDelegate fnFileRead,
            FdiFileWriteDelegate fnFileWrite,
            FdiFileCloseDelegate fnFileClose,
            FdiFileSeekDelegate fnFileSeek,
            int cpuType,
            [MarshalAs(UnmanagedType.LPStruct)] CabError erf);

        [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FDIIsCabinet", CharSet = CharSet.Ansi)]
        protected static extern bool FdiIsCabinet(
            IntPtr hfdi,
            IntPtr hf,
            [MarshalAs(UnmanagedType.LPStruct)] FdiCabinetInfo cabInfo);

        [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FDIDestroy", CharSet = CharSet.Ansi)]
        protected static extern bool FdiDestroy(IntPtr hfdi);

        [DllImport("cabinet.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "FDICopy", CharSet = CharSet.Ansi)]
        protected static extern bool FdiCopy(
            IntPtr hfdi,
            string cabinetName,
            string cabinetPath,
            int flags,
            FdiNotifyDelegate fnNotify,
            IntPtr fnDecrypt,
            IntPtr userData);

        protected const int CPU_TYPE_UNKNOWN = -1;

        protected FdiFileCloseDelegate _fileCloseDelegate;
        protected FdiFileOpenDelegate _fileOpenDelegate;
        protected FdiFileReadDelegate _fileReadDelegate;
        protected FdiFileSeekDelegate _fileSeekDelegate;
        protected FdiFileWriteDelegate _fileWriteDelegate;
        protected FdiMemAllocDelegate _memAllocDelegate;
        protected FdiMemFreeDelegate _memFreeDelegate;

        protected readonly CabError _error = new CabError();
        protected IntPtr _hContext;
        protected bool _bDisposed;

        protected virtual void SetDelegates()
        {
            _memAllocDelegate = FdiGenericCallbacks.MemAlloc;
            _memFreeDelegate = FdiGenericCallbacks.MemFree;
            _fileReadDelegate = FdiGenericCallbacks.FileRead;
            _fileOpenDelegate = FdiGenericCallbacks.FileOpen;
            _fileSeekDelegate = FdiGenericCallbacks.FileSeek;
            _fileWriteDelegate = FdiGenericCallbacks.FileWrite;
            _fileCloseDelegate = FdiGenericCallbacks.FileClose;
        }

        protected void CreateContext()
        {
            _hContext = FdiCreate(_memAllocDelegate, _memFreeDelegate, _fileOpenDelegate, _fileReadDelegate, _fileWriteDelegate, _fileCloseDelegate, _fileSeekDelegate, CPU_TYPE_UNKNOWN, _error);
            if (_hContext == IntPtr.Zero)
                throw new ApplicationException("Failed to create FDI context!");
        }

        public virtual void Dispose()
        {
            if (_hContext != IntPtr.Zero)
            {
                FdiDestroy(_hContext);
                _hContext = IntPtr.Zero;
                _bDisposed = true;
            }
        }

    }
}
